﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;


//参考代码：http://www.tuicool.com/articles/RBJFRn
namespace MinesweeperClient
{
    public partial class Form1 : Form
    {

        int[] dx = new int[] { -1, 0, 1, -1, 1, -1, 0, 1 };   // x坐标偏移量
        int[] dy = new int[] { 1, 1, 1, 0, 0, -1, -1, -1 };   // y坐标偏移量
        int[] px = new int[] { 1, -1, 0, 0, 1, -1, 1, -1 };   // 四方向x坐标偏移量
        int[] py = new int[] { 0, 0, 1, -1, 1, 1, -1, -1 };   // 四方向y坐标偏移量

        public int nWidth=10;     // 表示雷区的宽度
        public int nHeight=10;        // 表示雷区的高度
        public int nMineCnt=10;       // 表示地雷的数目

        bool bMouseLeft;    // 鼠标左键是否被按下
        bool bMouseRight;   // 鼠标右键是否被按下

        bool bGame; // 游戏是否结束
        const int MAX_WIDTH = 64;   // 最大宽度
        const int MAX_HEIGHT = 32;  // 最大高度
        int[,] pMine = new int[MAX_WIDTH, MAX_HEIGHT];  // 第一类数据
        int[,] pState = new int[MAX_WIDTH, MAX_HEIGHT]; // 第二类数据
        Point MouseFocus;   // 高亮点记录


        public Form1()
        {
            InitializeComponent();
            this.DoubleBuffered = true;
            newGame_Click(new object(), new EventArgs());
            ipadr = IPAddress.Loopback;
        }
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            
        }
        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            PaintGame(e.Graphics);
        }
        private void PaintGame(Graphics g)
        {
            g.Clear(Color.White);   // 清空绘图区
            // 我们需要是雷区在用户显示的区域上下左右保持6px的偏移量，使得整体看起来更加协调
            int nOffsetX = 6;   // X方向偏移量
            int nOffsetY = 6;   // Y方向偏移量
            for (int i = 1; i <= nWidth; i++)    // 绘制行
            {
                for (int j = 1; j <= nHeight; j++)   // 绘制列
                {
                    // 第一个参数为笔刷，这里采用内置笔刷SandyBronw
                    // 第二个参数为方块的参数，这里采用左上角坐标以及长宽的形式给出
                    // 34表示每个雷区的大小，再加上偏移量就是我们当前雷区的起始位置，由于要空出1px的间隔，因此还需要加1
                    // 由此可以的到每个方块在雷区中的位置，然后利用循环绘制出来
                    if (pState[i, j] != 1)   // 未点开
                    {
                        // 绘制背景
                        if (i == MouseFocus.X && j == MouseFocus.Y)  // 是否为高亮点
                        {
                            g.FillRectangle(new SolidBrush(Color.FromArgb(100, Color.SandyBrown)), new Rectangle(nOffsetX + 34 * (i - 1) + 1, nOffsetY + 34 * (j - 1) + 1, 32, 32));
                        }
                        else
                        {
                            g.FillRectangle(Brushes.SandyBrown, new Rectangle(nOffsetX + 34 * (i - 1) + 1, nOffsetY + 34 * (j - 1) + 1, 32, 32));   // 绘制雷区方块
                        }
                        // 绘制标记
                        if (pState[i, j] == 2)
                        {
                            g.DrawImage(Properties.Resources.Flag, nOffsetX + 34 * (i - 1) + 1 + 4, nOffsetY + 34 * (j - 1) + 1 + 2);   // 绘制红旗
                        }
                        if (pState[i, j] == 3)
                        {
                            g.DrawImage(Properties.Resources.Doubt, nOffsetX + 34 * (i - 1) + 1 + 4, nOffsetY + 34 * (j - 1) + 1 + 2);   // 绘制问号
                        }
                    }
                    else if (pState[i, j] == 1)  // 点开
                    {
                        // 绘制背景
                        if (MouseFocus.X == i && MouseFocus.Y == j)
                        {
                            g.FillRectangle(new SolidBrush(Color.FromArgb(100, Color.LightGray)), new Rectangle(nOffsetX + 34 * (i - 1) + 1, nOffsetY + 34 * (j - 1) + 1, 32, 32));
                        }
                        else
                        {
                            g.FillRectangle(Brushes.LightGray, new Rectangle(nOffsetX + 34 * (i - 1) + 1, nOffsetY + 34 * (j - 1) + 1, 32, 32));
                        }
                        // 绘制数字
                        if (pMine[i, j] > 0)
                        {
                            Brush DrawBrush = new SolidBrush(Color.Blue);    // 定义钢笔
                            // 各个地雷数目的颜色
                            if (pMine[i, j] == 2) { DrawBrush = new SolidBrush(Color.Green); }
                            if (pMine[i, j] == 3) { DrawBrush = new SolidBrush(Color.Red); }
                            if (pMine[i, j] == 4) { DrawBrush = new SolidBrush(Color.DarkBlue); }
                            if (pMine[i, j] == 5) { DrawBrush = new SolidBrush(Color.DarkRed); }
                            if (pMine[i, j] == 6) { DrawBrush = new SolidBrush(Color.DarkSeaGreen); }
                            if (pMine[i, j] == 7) { DrawBrush = new SolidBrush(Color.Black); }
                            if (pMine[i, j] == 8) { DrawBrush = new SolidBrush(Color.DarkGray); }
                            SizeF Size = g.MeasureString(pMine[i, j].ToString(), new Font("Consolas", 16));
                            g.DrawString(pMine[i, j].ToString(), new Font("Consolas", 16), DrawBrush, nOffsetX + 34 * (i - 1) + 1 + (32 - Size.Width) / 2, nOffsetY + 34 * (j - 1) + 1 + (32 - Size.Height) / 2);
                        }
                        // 绘制地雷
                        if (pMine[i, j] == -1)
                        {
                            g.DrawImage(Properties.Resources.Mine, nOffsetX + 34 * (i - 1) + 1 + 4, nOffsetY + 34 * (j - 1) + 1 + 2);   // 绘制地雷
                        }
                    }
                }
            }
        }
        private void newGame_Click(object sender, EventArgs e)
        {
            // 以下两行清空数组
            Array.Clear(pMine, 0, pMine.Length);
            Array.Clear(pState, 0, pState.Length);
            // 重置高亮点
            MouseFocus.X = 0; MouseFocus.Y = 0;
            // 初始化地雷数据
            Random Rand = new Random();
            for (int i = 1; i <= nMineCnt;)  // 地雷总数
            {
                // 随机地雷坐标(x, y)
                int x = Rand.Next(nWidth) + 1;
                int y = Rand.Next(nHeight) + 1;
                if (pMine[x, y] != -1)
                {
                    pMine[x, y] = -1; i++;
                }
            }
            for (int i = 1; i <= nWidth; i++)    // 枚举宽度
            {
                for (int j = 1; j <= nHeight; j++)   // 枚举高度
                {
                    if (pMine[i, j] != -1)   // 不是地雷，显示周围地雷数
                    {
                        for (int k = 0; k < 8; k++)  // 八个方向拓展
                        {
                            if (pMine[i + dx[k], j + dy[k]] == -1)   // 找到地雷
                            {
                                pMine[i, j]++;  // 地雷数自增
                            }
                        }
                    }
                }
            }
            Label_Main.Text = nMineCnt.ToString();  // 显示地雷数目
            bGame = false;  // 游戏暂未结束
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.X < 6 || e.X > 6 + nWidth * 34 ||
                e.Y < 6  ||
                e.Y > 6  + nHeight * 34) // 不在地雷区域
            {
                MouseFocus.X = 0; MouseFocus.Y = 0;
            }
            else
            {
                int x = (e.X - 6) / 34 + 1; // 获取x位置
                int y = (e.Y - 6) / 34 + 1; // 获取y位置
                MouseFocus.X = x; MouseFocus.Y = y; // 设置当前高亮点
            }
            this.Refresh();    // 重绘雷区
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)   // 鼠标左键被按下
            {
                bMouseLeft = true;
            }
            if (e.Button == MouseButtons.Right)  // 鼠标右键被按下
            {
                bMouseRight = true;
            }
        }

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            if ((MouseFocus.X == 0 && MouseFocus.Y == 0) || bGame) // 不在地雷区域或游戏结束
            {
                return; // 不做任何处理
            }

            if (bMouseLeft && bMouseRight)   // 左右键同时按下
            {
                if (pState[MouseFocus.X, MouseFocus.Y] == 1 && pMine[MouseFocus.X, MouseFocus.Y] > 0)    // 为数字区域
                {
                    int nFlagCnt = 0, nDoubtCnt = 0, nSysCnt = pMine[MouseFocus.X, MouseFocus.Y];   // 记录红旗数目，问号数目，九宫格地雷数目
                    for (int i = 0; i < 8; i++)
                    {
                        // 获取偏移量
                        int x = MouseFocus.X + dx[i];
                        int y = MouseFocus.Y + dy[i];
                        if (pState[x, y] == 2)   // 红旗
                        {
                            nFlagCnt++;
                        }
                        if (pState[x, y] == 3)   // 问号
                        {
                            nDoubtCnt++;
                        }
                        if (nFlagCnt == nSysCnt || nFlagCnt + nDoubtCnt == nSysCnt) // 打开九宫格
                        {
                            bool bFlag = OpenMine(MouseFocus.X, MouseFocus.Y);
                            if (!bFlag) // 周围有地雷
                            {
                                // 结束游戏 
                                GameLost();
                            }
                        }
                    }
                }
            }
            else if (bMouseLeft) // 左键被按下
            {
                if (pMine[MouseFocus.X, MouseFocus.Y] != -1)
                {
                    if (pState[MouseFocus.X, MouseFocus.Y] == 0)
                    {
                        dfs(MouseFocus.X, MouseFocus.Y);
                    }
                }
                else
                {
                    // 地雷，游戏结束
                    GameLost();
                }
            }
            else if (bMouseRight)    // 右键被按下
            {
                
                    if (pState[MouseFocus.X, MouseFocus.Y] == 0) // 未点开
                    {
                        if (Convert.ToInt32(Label_Main.Text) > 0)
                        {
                            pState[MouseFocus.X, MouseFocus.Y] = 2; // 红旗
                            Label_Main.Text = Convert.ToString(Convert.ToInt32(Label_Main.Text) - 1);   // 剩余地雷数目减1
                        }
                    }
                    else if (pState[MouseFocus.X, MouseFocus.Y] == 2) // 红旗
                    {
                        pState[MouseFocus.X, MouseFocus.Y] = 3; // 问号
                        Label_Main.Text = Convert.ToString(Convert.ToInt32(Label_Main.Text) + 1);   // 剩余地雷数目加1
                    }
                    else if (pState[MouseFocus.X, MouseFocus.Y] == 3) // 问号
                    {
                        pState[MouseFocus.X, MouseFocus.Y] = 0;  // 未点开
                    }
                
            }
            this.Refresh();
            GameWin();
            bMouseLeft = bMouseRight = false;
        }

        private void dfs(int sx, int sy)
        {
            pState[sx, sy] = 1; // 访问该点
            for (int i = 0; i < 8; i++)
            {
                // 获取相邻点的坐标
                int x = sx + px[i];
                int y = sy + py[i];
                if (x >= 1 && x <= nWidth && y >= 1 && y <= nHeight &&
                    pMine[x, y] != -1 && pMine[sx, sy] == 0 &&
                    (pState[x, y] == 0 || pState[x, y] == 3)) // 不是地雷，处于地雷区域，且未点开，或者标记为问号
                {
                    dfs(x, y);  // 访问该点
                }
            }
        }

        private bool OpenMine(int sx, int sy)
        {
            bool bFlag = true;  // 默认周围无雷
            for (int i = 0; i < 8; i++)
            {
                // 获取偏移量
                int x = MouseFocus.X + dx[i];
                int y = MouseFocus.Y + dy[i];
                if (pState[x, y] == 0)   // 问号
                {
                    pState[x, y] = 1;   // 打开
                    if (pMine[x, y] != -1)   // 无地雷
                    {
                        dfs(x, y);
                    }
                    else    // 有地雷
                    {
                        bFlag = false;
                        break;
                    }
                }
            }
            return bFlag;
        }

        private void GameLost()
        {
            for (int i = 1; i <= nWidth; i++)
            {
                for (int j = 1; j <= nHeight; j++)
                {
                    if (pMine[i, j] == -1 && (pState[i, j] == 0 || pState[i, j] == 3))   // 未点开或者标记为问号的地雷
                    {
                        pState[i, j] = 1;   // 点开该地雷
                    }
                }
            }

            bGame = true;
        }

        private void GameWin()
        {
            int nCnt = 0;   // 用户标记红旗数目、问号数目、以及无标记未点开区域总数
            for (int i = 1; i <= nWidth; i++)
            {
                for (int j = 1; j <= nHeight; j++)
                {
                    if (pState[i, j] == 0 || pState[i, j] == 2 || pState[i, j] == 3) // 对应无标记未点开区域、红旗区域、问号区域
                    {
                        nCnt++;
                    }
                }
            }
            if (nCnt == nMineCnt)    // 胜利条件
            {
               
                MessageBox.Show("游戏胜利", "提示", MessageBoxButtons.OK);
                bGame = true;
            }
        }
    
        Socket clientSocket = null;
        static Boolean isListen = true;
        Thread thDataFromServer;
        IPAddress ipadr;

        private void btnSend_Click(object sender, EventArgs e)
        {
            SendMessage();
        }
        private void SendMessage()
        {
            if (String.IsNullOrWhiteSpace(txtSendMsg.Text.Trim()))
            {
                MessageBox.Show("发送内容不能为空哦~");
                return;
            }
            if (clientSocket != null && clientSocket.Connected)
            {
                Byte[] bytesSend = Encoding.UTF8.GetBytes(txtSendMsg.Text + "$");
                clientSocket.Send(bytesSend);
                txtSendMsg.Text  = "";
            }
            else
            {
                MessageBox.Show("未连接服务器或者服务器已停止，请联系管理员~");
                return;
            }
        }


        //每一个连接的客户端必须设置一个唯一的用户名，在服务器端是把用户名和套接字保存在Dictionary<userName,ClientSocket>中
        private void btnConnect_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrWhiteSpace(txtName.Text.Trim()))
            {
                MessageBox.Show("请设置个用户名哦亲");
                return;
            }
            if (txtName.Text.Length>=17 && txtName.Text.ToString().Trim().Substring(0, 17).Equals("Server has closed"))
            {
                MessageBox.Show("该用户名中包含敏感词，请更换用户名后重试");
                return;
            }

            if (clientSocket == null || !clientSocket.Connected)
            {
                try
                {
                    clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    //参考网址： https://msdn.microsoft.com/zh-cn/library/6aeby4wt.aspx
                    // Socket.BeginConnect 方法 (String, Int32, AsyncCallback, Object)
                    //开始一个对远程主机连接的异步请求
                    /* string host,     远程主机名
                     * int port,        远程主机的端口
                     * AsyncCallback requestCallback,   一个 AsyncCallback 委托，它引用连接操作完成时要调用的方法，也是一个异步的操作
                     * object state     一个用户定义对象，其中包含连接操作的相关信息。 当操作完成时，此对象会被传递给 requestCallback 委托
                     */
                    //如果txtIP里面有值，就选择填入的IP作为服务器IP，不填的话就默认是本机的
                    if (!String.IsNullOrWhiteSpace(txtIP.Text.ToString().Trim()))
                    {
                        try
                        {
                            ipadr = IPAddress.Parse(txtIP.Text.ToString().Trim());
                        }
                        catch
                        {
                            MessageBox.Show("请输入正确的IP后重试");
                            return;
                        }
                    }
                    else
                    {
                        ipadr = IPAddress.Loopback;
                    }
                    //IPAddress ipadr = IPAddress.Parse("192.168.1.100");
                    clientSocket.BeginConnect(ipadr, 8080, (args) =>
                    {
                        if (args.IsCompleted)   //判断该异步操作是否执行完毕
                        {
                            Byte[] bytesSend = new Byte[4096];
                            txtName.BeginInvoke(new Action(() =>
                            {
                                bytesSend = Encoding.UTF8.GetBytes(txtName.Text.Trim() + "$");  //用户名，这里是刚刚连接上时需要传过去
                                if (clientSocket != null && clientSocket.Connected)
                                {
                                    clientSocket.Send(bytesSend);
                                    txtName.Enabled = false;    //设置为不能再改名字了
                                    txtSendMsg.Focus();         //将焦点放在
                                    thDataFromServer = new Thread(DataFromServer);
                                    thDataFromServer.IsBackground = true;
                                    thDataFromServer.Start();
                                }
                                else
                                {
                                    MessageBox.Show("服务器已关闭");
                                }

                            }));
                            txtIP.BeginInvoke(new Action(() =>
                            {
                                if (clientSocket != null && clientSocket.Connected)
                                {
                                    txtIP.Enabled = false;
                                }
                            }));
                        }
                    },null);
                }
                catch (SocketException ex)
                {
                    MessageBox.Show(ex.ToString());
                }
            }
            else
            {
                MessageBox.Show("你已经连接上服务器了");
            }
        }

        private void ShowMsg(String msg)
        {
            txtReceiveMsg.BeginInvoke(new Action(() =>
            {
                txtReceiveMsg.Text += Environment.NewLine + msg;    // 在 Windows 环境中，C# 语言 Environment.NewLine == "\r\n" 结果为 true
                //txtReceiveMsg.ScrollToCaret();
            }));
        }

        //获取服务器端的消息
        private void DataFromServer()
        {
            ShowMsg("Connected to the Chat Server...");
            isListen = true;
            try
            {
                while (isListen)
                {
                    Byte[] bytesFrom = new Byte[4096];
                    Int32 len = clientSocket.Receive(bytesFrom);

                    String dataFromClient = Encoding.UTF8.GetString(bytesFrom, 0, len);
                    if (!String.IsNullOrWhiteSpace(dataFromClient))
                    {
                        //如果收到服务器已经关闭的消息，那么就把客户端接口关了，免得出错，并在客户端界面上显示出来
                        if (dataFromClient.ToString().Length >=17 && dataFromClient.ToString().Substring(0, 17).Equals("Server has closed"))
                        {
                            clientSocket.Close();
                            clientSocket = null;

                            txtReceiveMsg.BeginInvoke(new Action(() =>
                            {
                                txtReceiveMsg.Text += Environment.NewLine + "服务器已关闭";
                            }));

                            txtName.BeginInvoke(new Action(() =>
                            {
                                txtName.Enabled = true;
                            }));    //重连当然可以换用户名啦

                            txtIP.BeginInvoke(new Action(() =>
                            {
                                
                                    txtIP.Enabled = true;
                                
                            }));

                            thDataFromServer.Abort();   //这一句必须放在最后，不然这个进程都关了后面的就不会执行了
                          
                            return;
                        }


                        if (dataFromClient.StartsWith("#") && dataFromClient.EndsWith("#"))
                        {
                            String userName = dataFromClient.Substring(1, dataFromClient.Length - 2);
                            this.BeginInvoke(new Action(() =>
                            {
                                MessageBox.Show("用户名：[" + userName + "]已经存在，请尝试其他用户名并重试");

                            }));
                            isListen = false;

                            txtName.BeginInvoke(new Action(() =>
                            {
                                txtName.Enabled = true;
                                clientSocket.Send(Encoding.UTF8.GetBytes("$"));
                                clientSocket.Close();
                                clientSocket = null;
                            }));
                            txtIP.BeginInvoke(new Action(() =>
                            {
                                
                                    txtIP.Enabled = true;
                                
                            }));

                        }
                        else
                        {
                            //txtName.Enabled = false;    //当用户名唯一时才禁止再次输入用户名
                            ShowMsg(dataFromClient);
                        }
                    }
                }
            }
            catch (SocketException ex)
            {
                isListen = false;
                if (clientSocket != null && clientSocket.Connected)
                {
                    //没有在客户端关闭连接，而是给服务器发送一个消息，在服务器端关闭连接
                    //这样可以将异常的处理放到服务器。客户端关闭会让客户端和服务器都抛异常
                    clientSocket.Send(Encoding.UTF8.GetBytes("$"));
                    MessageBox.Show(ex.ToString());
                }
            }
        }

        private void Form1_Activated(object sender, EventArgs e)
        {
            txtSendMsg.Focus();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (clientSocket != null && clientSocket.Connected)
            {
                clientSocket.Send(Encoding.UTF8.GetBytes("$"));
            }
        }

        private void btnBreak_Click(object sender, EventArgs e)
        {
            if (clientSocket != null && clientSocket.Connected)
            {
                thDataFromServer.Abort();
                clientSocket.Send(Encoding.UTF8.GetBytes("$"));

                clientSocket.Close();
                clientSocket = null;
           
                txtReceiveMsg.BeginInvoke(new Action(() =>
                {
                    txtReceiveMsg.Text += Environment.NewLine + "已断开与服务器的连接";
                    
                }));

                txtName.BeginInvoke(new Action(() =>
                {
                    txtName.Enabled = true;
                }));    //重连当然可以换用户名啦
                txtIP.BeginInvoke(new Action(() =>
                {
                    
                       txtIP.Enabled = true;
                    
                }));

                  
            }
        }

        
    }
}
